package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.OrderRecode;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.activity.UserRecode;
import com.atguigu.gmall.model.order.OrderDetail;
import com.atguigu.gmall.model.product.SkuInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author atguigu-mqx
 * @Description TODO
 * @Date 2023/8/1 10:13
 * @Version 1.0
 */
@Service
public class SeckillGoodsServiceImpl implements SeckillGoodsService {

    //  查询所有秒杀商品列表
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Override
    public List<SeckillGoods> findAll() {
        //  查询所有 hvals key
        List<SeckillGoods> seckillGoodsList = this.redisTemplate.opsForHash().values(RedisConst.SECKILL_GOODS);
        return seckillGoodsList;
    }

    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        //  获取某一个秒杀商品 hget key field value;
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS,skuId.toString());
        return seckillGoods;
    }

    @Override
    public Result seckillOrder(Long skuId, String userId,String skuIdStr) {
        //  校验抢购码！
        if (!skuIdStr.equals(MD5.encrypt(userId))){
            //  不相等，返回信息提示
            return Result.fail().message("校验抢购码失败，非法抢购");
        }
        //  校验状态位 skuId:status  25:1 或 25:0
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) ){
            return Result.fail().message("状态位异常");
        }else if ("0".equals(status)){
            return Result.fail().message("商品已售罄");
        }else {
            //  可以抢购，则将用户信息，放入队列中.
            UserRecode userRecode = new UserRecode();
            userRecode.setSkuId(skuId);
            userRecode.setUserId(userId);
            //  调用消息工具类
            this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_USER,MqConst.ROUTING_SECKILL_USER,userRecode);
            //  返回数据
            return Result.ok();
        }
    }

    @Override
    public void seckillUser(UserRecode userRecode) {
        //  1.  判断状态位
        String status = (String) CacheHelper.get(userRecode.getSkuId().toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            return;
        }
        //  2.  判断用户是否秒杀过，每个用户限制只能抢购一次！ setnx key value;
        String seckillUserKey = RedisConst.SECKILL_USER+userRecode.getUserId()+userRecode.getSkuId();
        //  在1小时内不能重复购买！
        Boolean exist = this.redisTemplate.opsForValue().setIfAbsent(seckillUserKey,userRecode.getUserId(),RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);
        //  exist = true; 说明第一次抢购！
        if (!exist){
            return;
        }
        //  3.  减库存 rpop key
        String skuIdExist = (String) this.redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId());
        //  判断
        if (StringUtils.isEmpty(skuIdExist)){
            //  说明售罄. 必须通知其他兄弟节点 skuId:0
            this.redisTemplate.convertAndSend("seckillpush",userRecode.getSkuId()+":0");
            return;
        }

        //  自定义一个实体类来存储秒杀信息数据.
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userRecode.getUserId());
        //  在秒杀的时候页面没有给购买数量入口，在购买的时候，限制没人只能购买1件！
        orderRecode.setNum(1);
        //  下单码
        orderRecode.setOrderStr(MD5.encrypt(userRecode.getUserId()+userRecode.getSkuId()));
        orderRecode.setSeckillGoods(this.getSeckillGoods(userRecode.getSkuId()));

        //  存储到缓存. hset key field value;
        //  只能秒杀一件
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,userRecode.getUserId(),orderRecode);
        //  购买多件。
        //  String field = userRecode.getUserId()+userRecode.getSkuId();
        //  this.redisTemplate.opsForHash().put(RedisConst.SECKILL_ORDERS,field,orderRecode);

        //  异步修改库存：redis-mysql
        this.rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK,MqConst.ROUTING_SECKILL_STOCK,userRecode.getSkuId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStock(Long skuId) {
        //  更新mysql - redis;
        //  获取缓存中的数据. hget key field
        SeckillGoods seckillGoods = (SeckillGoods) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_GOODS, skuId.toString());
        //  现在还有多少库存? 查看队列的长度。
        Long count = this.redisTemplate.opsForList().size(RedisConst.SECKILL_STOCK_PREFIX + skuId);
        //  属于更新了缓存。
        seckillGoods.setStockCount(count.intValue());
        //  更新数据库
        this.seckillGoodsMapper.updateById(seckillGoods);
        //  写回缓存 hget key field value;
        this.redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS,skuId.toString(),seckillGoods);
    }

    @Override
    public Result checkOrder(Long skuId, String userId) {
        //  判断用户是否在缓存中存在.
        String seckillUserKey = RedisConst.SECKILL_USER+userId+skuId;
        Boolean existUserKey = this.redisTemplate.hasKey(seckillUserKey);
        if (existUserKey){
            //  说明用户 key 存在，只有存在的情况下，才有可能秒杀成功.
            OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
            //  判断是否有预习单数据
            if (null != orderRecode){
                //  说明这个用户秒杀成功。
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }
        //  判断这个用户是否有过真的下单记录，到提交订单的时候才叫真正下单 {redis-存储; mysql-存储。}
        //  判断缓存中有是否有真正下单记录就可以了。
        //  key = seckill:orders:users field=userId value=orderId
        String orderId = (String) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS_USERS, userId);
        //  判断
        if (null != orderId){
            //  说明这个用户秒杀成功已经有过记录。
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }
        //  判断一个状态位
        String status = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isEmpty(status) || "0".equals(status)){
            //  说明这个用户秒杀成功已经有过记录。
            return Result.build(null, ResultCodeEnum.SECKILL_FAIL);
        }
        //  默认排队中.
        return  Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }

    @Override
    public Map authTrade(String userId) {
        //  声明一个map 集合
        Map<String, Object> map = new HashMap<>();
        //  获取到秒杀商品，赋值给detailArrayList 集合;
        OrderRecode orderRecode = (OrderRecode) this.redisTemplate.opsForHash().get(RedisConst.SECKILL_ORDERS, userId);
        ArrayList<OrderDetail> detailArrayList = new ArrayList<OrderDetail>();
        //  秒杀清单--渲染数据的实体应该是orderDetail;
        SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setSkuId(seckillGoods.getSkuId());
        orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderDetail.setSkuName(seckillGoods.getSkuName());
        orderDetail.setOrderPrice(seckillGoods.getCostPrice());
        orderDetail.setSkuNum(orderRecode.getNum());
        detailArrayList.add(orderDetail);
        //  给map 赋值.
        //  秒杀商品列表
        map.put("detailArrayList",detailArrayList);
        map.put("totalNum",orderRecode.getNum());
        map.put("totalAmount",seckillGoods.getCostPrice());
        return map;
    }
}
